package com.chongaizhen.sharding.code.configuration;

import java.sql.SQLException;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.InlineShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import com.alibaba.druid.pool.DruidDataSource;
import com.google.common.collect.Maps;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * @author chongaizhen
 * @date 2021-07-14
 */
@Slf4j
@MapperScan(basePackages = "com.chongaizhen.sharding.code.mapper", sqlSessionFactoryRef = "shardingSqlSessionFactory", sqlSessionTemplateRef =
        "shardingSqlSessionTemplate")
@Configuration
public class ShardingDatasourceConfiguration {

    /**
     * 终端ID
     */
    private static long wokerId = 1;

    /**
     * 数据中心ID
     */
    private static long datacenterId = 1;

    private Snowflake defaultInstance = null;

    @Bean("db0")
    @ConfigurationProperties(prefix = "spring.datasource.db0")
    public DataSource getDataSource0() {
        return new DruidDataSource();
    }

    @Bean("db1")
    @ConfigurationProperties(prefix = "spring.datasource.db1")
    public DataSource getDataSource1() {
        return new DruidDataSource();
    }

    @DependsOn({"db0", "db1"})
    @Bean("shardingDataSource")
    public DataSource customShardingDataSource(@Qualifier("db0") DataSource ds0,
                                               @Qualifier("db1") DataSource ds1) throws SQLException {
        Map<String, DataSource> dataSource = Maps.newHashMap();
        dataSource.put("ds0", ds0);
        dataSource.put("ds1", ds1);
        return buildShardingDataSource(dataSource);
    }

    private DataSource buildShardingDataSource(Map<String, DataSource> dataSourceMap) throws SQLException {

        String LOGIC_TABLE = "user";

        String actualDataNodes = "ds${0..1}.user_${0..1}";

        TableRuleConfiguration tableRule = new TableRuleConfiguration(LOGIC_TABLE, actualDataNodes);
        MyPreciseShardingAlgorithm myPreciseShardingAlgorithm = new MyPreciseShardingAlgorithm();

        /**
         * 输出如下日志：（第一行路由是db，下一行是table）
         *
         * 2018-01-19 20:13:36,790 -2 collection:["db0","db1","db2"],preciseShardingValue:{"columnName":"order_id",
         * "logicTableName":"t_order_items","value":100}
         * 2018-01-21 16:33:22,269 -2 collection:["t_order_items_0","t_order_items_1"],preciseShardingValue:{
         * "columnName":"order_id","logicTableName":"t_order_items","value":100}
         */

        //配置分库策略(行表达式，也可以使用自定义分片策略类)
        //tableRule.setDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("id", "ds$->{id % 2}"));
        tableRule.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("id",
                myPreciseShardingAlgorithm));
        //配置分表策略
        tableRule.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("id",
                myPreciseShardingAlgorithm));

        // 配置分片规则
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTableRuleConfigs().add(tableRule);

        // 配置其他表规则...
        //shardingRuleConfig.getTableRuleConfigs().add(otherTableRule);

        //配置读写分离规则
        //shardingRuleConfig.setMasterSlaveRuleConfigs();

        shardingRuleConfig.setDefaultDataSourceName("ds0");

        //属性配置项，可以为以下属性
        Properties propertie = new Properties();
        //是否打印SQL解析和改写日志
        propertie.setProperty("sql.show", Boolean.TRUE.toString());
        //用于SQL执行的工作线程数量，为零则表示无限制
        propertie.setProperty("executor.size", "4");
        //每个物理数据库为每次查询分配的最大连接数量
        propertie.setProperty("max.connections.size.per.query", "1");
        //是否在启动时检查分表元数据一致性
        propertie.setProperty("check.table.metadata.enabled", "false");

        // 获取数据源对象
        DataSource dataSource = null;
        try {
            dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, propertie);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return dataSource;
    }

    @PostConstruct
    private void init() {
        try {
            wokerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr()) % 31;
            log.info("get local workerId success， workerId: {}", wokerId);
        } catch (Exception e) {
            log.warn("get local workerId fail，", e);
            wokerId = NetUtil.getLocalhostStr().hashCode() % 31;
        }
    }

    @Bean
    public Snowflake snowflake() {
        if (null != defaultInstance) {
            return defaultInstance;
        } else {
            synchronized(ShardingDatasourceConfiguration.class) {
                if (null != defaultInstance) {
                    return defaultInstance;
                } else {
                    defaultInstance = IdUtil.createSnowflake(wokerId, datacenterId);
                    return defaultInstance;
                }
            }
        }
    }

    /**
     * 创建SqlSessionFactory
     *
     * @return
     *
     * @throws Exception
     */
    @Bean(name = "shardingSqlSessionFactory")
    @Primary
    SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(customShardingDataSource(getDataSource0(), getDataSource1()));
        //注意：设置mybatis的xml所在位置
        //Invalid bound statement (not found): com.zhongjun.data02.mapper.User02Mapper.innet 不进行配置会报错
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
        SqlSessionFactory sqlSessionFactory = bean.getObject();
        assert Objects.nonNull(sqlSessionFactory);
        return sqlSessionFactory;
    }

    /**
     * 创建SqlSessionTemplate
     *
     * @return
     *
     * @throws Exception
     */
    @Bean(name = "shardingSqlSessionTemplate")
    @Primary
    SqlSessionTemplate sqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }
}
